import { memo, useMemo, useState, useCallback, useEffect } from "react";

// 使用包含增加项的元素创建模拟数据
const data = new Array(100)
    .fill()
    .map((_, i) => i + 1)
    .map((n) => ({
        id: n,
        name: `Item ${n}`
    }));

export default function App2() {
    // 包含所选item的数组
    const [selected, setSelected] = useState([]);

    // 选择或取消选择给定的item
    const toggleItem = (item) => {
        setSelected((prevSelected) => {
            if (!prevSelected.includes(item)) {
                return [...prevSelected, item];
            } else {
                return prevSelected.filter((current) => current !== item);
            }
        });
    }
    // useCallback缓存函数,第一个参数为被缓存的函数,第二个参数为变化依据,和useState一样若为[]则一直不变,若不传则每次都变等于没缓存...
    const toggleItem2 = useCallback(toggleItem, []);

    return (
        <div className="App">
            <h1>性能优化改 List Example</h1>
            <ListMemo data={data} selectedItems={selected} toggleItem={toggleItem2} setSelected={setSelected} />
        </div>
    );
}

// setSelected 的传入可以看出,并不会造成组件每次重新渲染,但自定义函数则会
const ListUseMemo = ({ data, selectedItems, toggleItem, setSelected }) => {
    return (
        <ul>
            {data.map((item) => useMemo(() => // useMemo缓存函数的结果,此处为组件函数计算的结果,第二个参数为缓存变化依据
                <ListItem
                    key={item.id}
                    item={item}
                    selected={selectedItems.includes(item)}
                    onClick={toggleItem}
                />, [item, selectedItems.includes(item), toggleItem]
            ))}
        </ul>
    );
};

const ListMemo = ({ data, selectedItems, toggleItem }) => {

    return (
        <ul>
            {data.map((item) => (
                <MemoizedListItem
                    key={item.id}
                    item={item}
                    selected={selectedItems.includes(item)}
                    onClick={toggleItem}
                />
            ))}
        </ul>
    );
};

const ListItem = ({ item, selected, onClick }) => {
    // 运行复杂的操作来模拟负载，在实际开发中，这可以是自定义的复杂的渲染。
    expensiveOperation(selected);

    return (
        <li
            style={selected ? { textDecoration: "line-through" } : undefined}
            onClick={() => onClick(item)}
        >
            {item.name}
        </li>
    );
};

// memo缓存组件,除非props变化才会重新渲染,通常情况下memo用起来比较方便
const MemoizedListItem = memo(ListItem);



// 运行复杂的操作来模拟负载，在实际开发中，这可以是自定义的复杂的渲染。
const expensiveOperation = (selected) => {
    // 这里我们使用 selected 只是因为我们要模拟一个依赖于 props 的操作
    let total = selected ? 1 : 0;
    for (let i = 0; i < 200000; i++) {
        total += Math.random();
    }
    return total;
};

/*
React.memo() 是一个高阶组件，使用它来包装我们不想重新渲染的组件，除非其中的 props 发生变化

useMemo() 是一个 React Hook，我们可以使用它在组件中包装函数。 我们可以使用它来确保该函数中的值仅在其依赖项之一发生变化时才重新计算

很多人都喜欢使用 useMemo 来处理开销较大的计算并保持引用相等


useCallback 缓存的是方法的引用,保障方法的引用相等
useMemo 缓存的则是方法的返回值/把组件当做数据缓存,保障结果的引用相等
memo 使用场景是减少不必要的子组件渲染。通过对2次的props进行比较，如果第二个参数不传递，则默认进行浅比较


useCallback
用来缓存函数，这个函数如果是由父组件传递给子组件，或者自定义hooks里面的函数【通常自定义hooks里面的函数，不会依赖于引用它的组件里面的数据】，这时候我们可以考虑缓存这个函数，好处：

1，不用每次重新声明新的函数，避免释放内存、分配内存的计算资源浪费
2，子组件不会因为这个函数的变动重新渲染。【和React.memo搭配使用】

由此可见，useCallback 主要用来缓存函数，虽然useMemo也能实现，但是callback写法直观。
const memoizedValue1 = useMemo(() => () => console.log('Hello world!'), []);
const memoizedValue2 = useCallback(() => console.log('Hello world!'), []);

```
安装React Developer Tools插件后打开Chrome 的devtools的profiler
-> 选择 Components
-> 勾选 Highlight updates when components render
```

性能优化都是有代价的。直到确实需要抽象或优化时才去做，这样可以避免承担成本而不会获得收益的情况。
使用useCallback 和 useMemo的成本是：对于你的同事来说，你使代码更复杂了；你可能在依赖项数组中犯了一个错误，并且你可能通过调用内置的 hook、并防止依赖项和 memoized 值被垃圾收集，而使性能变差。如果你获得了必要的性能收益，那么这些成本都是值得承担的，但请谨记非必要不优化这一原则。
单纯使用memo只是让组件拥有了 memoized 能力，而真正发挥出组件的 memoized 能力往往需要搭配useCallback 和 useMemo使用。

*/